package org.jnarcissus;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodProxy;

import org.hamcrest.Description;
import org.jbrisk.HashCodeBuilder;
import org.jbrisk.exceptions.NullArgumentException;

public class MonitoredInstanceMethodCall {

	private int callCount;
	
	private IMonitoredInstance source;

	private Method method;

	private Object lastCallReturnValue;
	
	private Object[] arguments;

	private MethodProxy methodProxy;
		
	private MonitoredInstanceMethodAssertBase methodCallAssert;

	public MonitoredInstanceMethodCall(IMonitoredInstance source, Method method, Object[] arguments, MethodProxy methodProxy) {

		super();

		if (source == null)
			throw new NullArgumentException("source");

		if (method == null)
			throw new NullArgumentException("method");

		if (methodProxy == null)
			throw new NullArgumentException("methodProxy");

		this.source = source;
		this.method = method;
		this.arguments = arguments;
		this.methodProxy = methodProxy;
	}
	
	private void appendArgumentValue(Description desc, Object arg) {
		
		if (arg != null) {

			if (arg instanceof String) {

				desc.appendText("\"");
				desc.appendText(arg.toString());
				desc.appendText("\"");

			} else {

				desc.appendText(arg.toString());
			}

		} else {

			desc.appendText("null");
		}
	}

	public void appendMethodDescription(Description desc) {

		// String ret = source.getClass().getSuperclass().getName() + "." +
		// method.getName() + "(";
		// String ret = "<" + source.toString() + ">." + method.getName();
		desc.appendText(getSource().toString());
		desc.appendText(" -> ");
		desc.appendText(getSource().getClass().getSuperclass().getName());
		desc.appendText(".");
		desc.appendText(getMethod().getName());
		desc.appendText("(");

		if (getArguments() != null && getArguments().length > 0) {

			appendArgumentValue(desc, getArguments()[0]);

			if (getArguments().length > 1) {

				for (int i = 1; i < getArguments().length; i++) {

					desc.appendText(", ");
					appendArgumentValue(desc, getArguments()[i]);
				}
			}
		}

		desc.appendText(")");
	}
	
	@Override
	public boolean equals(Object obj) {

		if (obj == null)
			return false;

		if (obj == this)
			return true;

		if (!(obj instanceof MonitoredInstanceMethodCall))
			return false;

		MonitoredInstanceMethodCall other = (MonitoredInstanceMethodCall) obj;

		boolean equals = getMethod().equals(other.getMethod());

		if (equals) {

			// If is the same method, check if the arguments are equal
			boolean hasArguments = getArguments() != null && getArguments().length > 0;
			boolean otherHasArguments = other.getArguments() != null && other.getArguments().length > 0;

			if (hasArguments && otherHasArguments) {

				// If both have arguments, compares all arguments
				equals = getArguments().length == other.getArguments().length;

				int i = 0;

				while (equals && i < getArguments().length) {
					
					Object arg = getArguments()[i];
					Object otherArg = other.getArguments()[i];
					
					equals = (arg == null && otherArg == null) || (arg != null && arg.equals(otherArg));

					i++;
				}

			} else if (hasArguments != otherHasArguments) {

				equals = false;
			}
		}

		return equals;
	}

	public void executeAsserts() {
		
		if (getMethodCallAssert() != null)
			getMethodCallAssert().execute();
	}

	public Object[] getArguments() {
		return arguments;
	}

	/**
	 * Returns the number of times this method was called.
	 * 
	 * @return Number of times this method was called.
	 */
	public int getCallCount() {

		return callCount;
	}

	public Object getLastCallReturnValue() {
		return lastCallReturnValue;
	}

	public Method getMethod() {
		return method;
	}

	public MonitoredInstanceMethodAssertBase getMethodCallAssert() {
		return methodCallAssert;
	}

	public MethodProxy getMethodProxy() {
		return methodProxy;
	}

	public IMonitoredInstance getSource() {
		return source;
	}

	@Override
	public int hashCode() {

		HashCodeBuilder builder = new HashCodeBuilder();

		builder.append(getMethod().hashCode());

		for (Class<?> argType : getMethod().getParameterTypes()) {

			builder.append(argType.hashCode());
		}

		if (getArguments() != null && getArguments().length > 0) {

			for (Object arg : getArguments()) {

				builder.append(arg);
			}
		}

		return builder.toHashCode();
	}

	public void incrementCallCount() {
		
		callCount++;
	}
	
	public void setLastCallReturnValue(Object lastCallReturnValue) {
		this.lastCallReturnValue = lastCallReturnValue;
	}
	
	public void setMethodCallAssert(MonitoredInstanceMethodAssertBase methodCallAssert) {
		this.methodCallAssert = methodCallAssert;
	}
}
